package Segment;

use strict;

use Stack;
use Class::DBI::AbstractSearch;
use base qw (NetworkDB);
use base qw (Class::Data::Inheritable);

use NetAddr::IP;

__PACKAGE__->set_up_table('segments');
#__PACKAGE__->has_a(networkid => 'Network');
__PACKAGE__->has_a(dhcp_serverid => 'DHCPserver');
__PACKAGE__->has_a(gatewayid => 'Gateway');
__PACKAGE__->has_a(idsid => 'IDS');
__PACKAGE__->has_many(plugs => 'Plug');

use Host;
use Logger;
use Data::Dumper;

my ($time_before, $time_after);

# call of this method is from Class/DBI.pm hacked by ostry
sub initialize {
	my $self = shift;
	$self->{'{ARPtableCreated}'} = 0;
}

# doplnkova funkcia po zmene DB modelu (povodne bol segment vlastnostou stacku, nie plugu)
sub stacks {
	my $self = shift;
        my @segmentstacks;
	Logger->write(__PACKAGE__."::Getting list of stacks with the same administrative domain as VLAN segment ".$self->subnet." (VLAN ".$self->segmentid.", ".$self->name.") . . .");	
	# 18.4.2007 - optimalizacia vyhladavania made by ostry - prehladavaju sa len tie stohy, ktore maju tu istu domenu, ako prislusny segment
	# TODO: osetrit, aby sa nemohli na stohoch nastavovat access VLAN nepatriace do domeny stohu
	my $domainid = $self->domainid;
	my @allstacks = Stack->search ( domainid => $domainid );
	Logger->write(__PACKAGE__."::Getting list done.");
	return @allstacks;
}

sub make_iptables_ACL {
	my $self = shift;
	my @plugs = Plug->search(segmentid => $self->segmentid, is_activated => 'true');
	my $iptables_ACL;
	foreach my $plug (@plugs) {
		my $iterator = Host->search(plugid => $plug->plugid);
		while (my $address = $iterator->next) {
			push @{$iptables_ACL->{$address->ip}}, $address->mac;
			push @{$iptables_ACL->{$address->ip}}, $plug->dump2line;
		}
	}
}

# doplnil ostry 14.4.2007, nakolko arp.dat casto obsahuje aj adresy z inych subnetov
sub is_my_IPaddr {	# zistenie, ci IP adresa patri do subnetu tohto LAN segmentu
	my $self = shift;
	my $IP = NetAddr::IP->new(shift);
	my $subnet = NetAddr::IP->new($self->subnet);
	if ($subnet->contains($IP)) {
		return 1; # true
	} else {
		return 0; # false
	}
}

sub MAC2IP {
	my $self = shift;
	my $MAC = shift;

	my $IP =  $self->ARPgetIP($MAC);	# zistenie IP z ARP
	if (defined $IP && $IP) {
		return $IP;
	} else {
		return 0;	# no valid IP
	}
}

sub ARPgetMAC {
	my $self = shift;
        my ($IP) = @_;
	my $reverseARPtable = $self->ARPtable->{reverse};
	if (defined $self->ARPtable->{reverse}->{$IP} && $self->ARPtable->{reverse}->{$IP}) {
								# bez tej podmienky by to zomrelo
								# v pripade, ze by sa $IP nenasla v Arpwatchu
		my $MAC =  $self->ARPtable->{reverse}->{$IP}->{MAC};
		return $MAC;
	} else {
		Logger->write(__PACKAGE__."::no MAC returned");
		return "";
	}
}

sub ARPgetIP {
        my $self = shift;
        my ($MAC) = @_;
	my $IP = $self->ARPtable->{direct}->{lc($MAC)}->{IP};
	return $IP;
}

sub ARPtable {	# vrati ARP tabulku (hash), ak nebola vytvorena, da ju vytvorit
	my $self = shift;
	if ((time - $self->{'{ARPtableCreated}'}) > 300) {
		$self->loadARPentries();
		$self->{'{ARPtableCreated}'} = time;
	}
	return $self->{'{ARPtable}'};
}

sub loadARPentries {	# vytiahne z LAN segmentu ARP tabulku, TODO: zovseobecnit a modularizovat
	my $self = shift;
	# deklaracia zakladnych premennych
	my ($ARPtable,$reverseARPtable,$timestamps);

	my $subnet = $self->subnet;
	my $gw_mgmt_IP = $self->idsid->mgmt_ip;
	my $gw_user = $self->idsid->default_user;
	my $arp_dat_path = $self->arp_dat_path;
	Logger->write(__PACKAGE__."Getting Arpwatch history for ".$self->subnet." (VLAN ".$self->segmentid.") . . . (in case of big delay ensure that $arp_dat_path exists on $gw_mgmt_IP)");	
        my @rawARPtable = `sudo -u $gw_user ssh $gw_user\@$gw_mgmt_IP 'cat $arp_dat_path'`;
	Logger->write(__PACKAGE__."::Getting Arpwatch history done.");
	Logger->write(__PACKAGE__."::Parsing Arpwatch history . . .");	
	foreach my $ARPline (@rawARPtable) {
		my ($MAC,$IP,$timestamp,$hostname) = split(/\s+/, $ARPline);
		if (!$self->is_my_IPaddr($IP)) { next; }
		$MAC = Utils::MACshort2normal($MAC);
		my $mac_update_entry = 0;
# troubleshooting:
		if ($ARPtable->{$MAC}) {	# ak uz zaznam pre MAC existuje
			if ($ARPtable->{$MAC}->{timestamp} < $timestamp) {	# ak je predosly neaktualny
				$mac_update_entry = 1;
				# print "neaktualny zaznam pre $MAC: ".$ARPtable->{$MAC}->{IP}." ".$ARPtable->{$MAC}->{timestamp}."\n";	#odkomentovat v pripade potreby analyzovat arp.dat
			} else {
				# print "neaktualny zaznam pre $MAC: $IP $timestamp \n"; #detto
			}
		} else {
			$mac_update_entry = 1;
		}
		if ($mac_update_entry) {
			$ARPtable->{lc($MAC)}->{IP} = $IP;
			$ARPtable->{lc($MAC)}->{timestamp} = $timestamp;
		}
		my $ip_update_entry = 0;
		if ($reverseARPtable->{$IP}) {	# ak uz zaznam pre MAC existuje
			if ($reverseARPtable->{$IP}->{timestamp} < $timestamp) {	# ak je predosly neaktualny
				$ip_update_entry = 1;
				#print "neakutalny zaznam pre $IP: ".$reverseARPtable->{$IP}->{MAC}." ".$reverseARPtable->{$IP}->{timestamp}."\n";	#detto
			} else {
				#print "neaktualny zaznam pre $IP: $MAC $timestamp \n";	#detto
			}
		} else {
			$ip_update_entry = 1;
		}
		if ($ip_update_entry) {
			$reverseARPtable->{$IP}->{MAC} = $MAC;
			$reverseARPtable->{$IP}->{timestamp} = $timestamp;
		}


	}
	$self->{'{ARPtable}'}->{direct} = $ARPtable;
	$self->{'{ARPtable}'}->{reverse} = $reverseARPtable;
	Logger->write(__PACKAGE__."::Parsing Arpwatch history done.");
}

sub MAC2Plug {
	my $self = shift;
	my ($MAC) = @_;
	Logger->write(__PACKAGE__." Searching $MAC on VLAN segment ".$self->segmentid." in progress...");
	foreach my $stack ($self->stacks) {
		# 3.8.2006 pridany druhy parameter Stack::MAC2Plug, nakolko pridavanemu Ciscu 2950 je potrebne stanovit aj VLAN pri lokalizacii adresy
		my $plug = $stack->MAC2Plug($MAC, $self->segmentid);
		if ($plug && $plug > 0 && !($plug->is_vlan_trunk)) {
			Logger->write(__PACKAGE__." Searching $MAC on VLAN segment ".$self->segmentid." done - $MAC found at plug ".$plug->plugid);
			return $plug;
		}
	}
	Logger->write(__PACKAGE__." Searching $MAC on VLAN segment ".$self->segmentid." done - $MAC not found.");
	return 0;
}

sub create_iptables_accounting_tree {
	my $self = shift;
	my ($net, $level, $chain) = @_;
#	my $chain_base_name = "VLAN".$self->segmentid;
	if (($net == undef) or !$net) {
		$net = NetAddr::IP->new($self->subnet);
	}

	my $main_chain = $self->iptables_chain_root;

	if ($chain eq undef) {
		$chain = $main_chain;
		# zaklad pre tvorbu chainov <chain>-in,out,acct-in,acct-out
	}
	my $maxmask = 27;				# sice "hardcoded", ale mozno sa s tou hodnotou vyhrat, pripadne ju mozem dat do databazy, ak sa to ukaze byt ucelnym - touto hodnotou sa da regulovat pocet chainov a pravidiel acct
	if ($level == undef) {
		$level = 0;
	}

#	if ($maxmask == undef) {
#		$maxmask = 32;
#	}


	my $address = $net->addr;
	my $masklen = $net->masklen;
	
	my $iptables;
	if ($level == 0) {
		$iptables = "#-F ${chain}-in\n#-F ${chain}-out\n\n";
	}

	my $spaces = Utils::spaces($level);

	my $submask = $masklen + 1;

	if ($submask < $maxmask) {
		my @subnets = $net->split($submask);
		my $subnetMARK = 0;	# 0 alebo 1, podla toho, ci sa jedna o prvu alebo druhu polovicu subnetu
		foreach my $subnet (@subnets) {
			my $subchain = $chain.$subnetMARK;
			$iptables .= "$spaces -F $subchain-in\n";	# vycisti stary chain
			$iptables .= "$spaces -X $subchain-in\n";	# zrus stary chain
			$iptables .= "$spaces -N $subchain-in\n";	# vytvor novy chain
			$iptables .= "$spaces -Z $subchain-in\n";	# vynuluj countery (pre istotu)
			$iptables .= "$spaces -A $chain-in -d ".$subnet->addr."/".$subnet->masklen." -j $subchain-in\n";
			$iptables .= "$spaces -F $subchain-ex\n";	# vycisti stary chain
			$iptables .= "$spaces -X $subchain-ex\n";	# zrus stary chain
			$iptables .= "$spaces -N $subchain-ex\n";	# vytvor novy chain
			$iptables .= "$spaces -Z $subchain-ex\n";	# vynuluj countery (pre istotu)
			$iptables .= "$spaces -A $chain-ex -s ".$subnet->addr."/".$subnet->masklen." -j $subchain-ex\n";
			my $sublevel = $level + 1;
			$iptables .= $self->create_iptables_accounting_tree($subnet, $sublevel, $subchain);
			$subnetMARK++;
		}
	} else {
		my @subnets = $net->split(32);
		foreach my $IP (@subnets) {
			$iptables .= "$spaces -A $chain-in -d ".$IP->addr." -j $main_chain-acct-in\n";
			$iptables .= "$spaces -A $chain-ex -s ".$IP->addr." -j $main_chain-acct-ex\n";
		}
	}

	return $iptables;
}

sub get_accounting_info {		# parsuje vystup z iptables na prislusnom firewalle a uklada do databazy
	my $self = shift;
	my $gw = $self->gatewayid;
	my $chain = $self->iptables_chain_root;
	my $chain_acct = $self->iptables_chain_root."-acct";
	my $hosts;
#	warn "Ziskavam aktualne accountingove informacie pre ".$self->subnet." (VLAN ".$self->segmentid.") . . .\n"; $time_before = time;	
	my @iptables_accounting_lines = $gw->command("sudo iptables -L -n -v -x | grep $chain_acct | grep -v references | sort -n");

	foreach my $line (@iptables_accounting_lines) {
		$line =~ /^[ ]*([0-9]*)[ ]*([0-9]*)[ ]*$chain\-acct.*all[ \-\*]*(\S*)[ ]*(\S*).*$/;
		my $packets = $1;
		my $bytes = $2;
		my $source = $3;
		my $destination = $4;
		if ($source ne '0.0.0.0/0') {
			$hosts->{$source}->{bytes_ex} = $bytes;
			$hosts->{$source}->{packets_ex} = $packets;
		} elsif ($destination ne '0.0.0.0/0') {
			$hosts->{$destination}->{bytes_in} = $bytes;
			$hosts->{$destination}->{packets_in} = $packets;
		} else {
			Logger->write(__PACKAGE__."::Destination & source <> 0.0.0.0, niekde sa stala chyba.");
		}
	}
#	warn "Hotovo. Operacia trvala ".(time - $time_before)." sekund.\n";
#	warn "Aktualizujem databazu accountingovych informacii ".$self->subnet." (VLAN ".$self->segmentid.") . . .\n"; $time_before = time;
	my $csv_output = "IP,upload_bytes,upload_packets,download_bytes,download_packets\n";
#	$self->db_Main->{AutoCommit => 0};	# vypnutie autocommitu transakcii
	foreach my $key (keys %{$hosts}) {
		my $host = Host->retrieve($key);		# vytiahni zaznam pre danu IP z databazy
		my $ip = $host->ip;
		if ($host eq undef) {				# ak taku IP nemame v databaze
			warn "$key unknown\n";
			next;
		}

		my $update_statement = "UPDATE addresses SET ";

                if ($hosts->{$key}->{bytes_in} < $host->bytes_in) {
			$update_statement .= "bytes_in_base = ".($host->bytes_in_base+$host->bytes_in).", ";
                        $host->bytes_in_base(($host->bytes_in_base + $host->bytes_in) || 0);
			$update_statement .= "packets_in_base = ".($host->packets_in_base+$host->packets_in).", ";
                        $host->packets_in_base(($host->packets_in_base + $host->packets_in) || 0);
                } else {
                }
		$host->bytes_in($hosts->{$key}->{bytes_in} || 0);
		$update_statement .= "bytes_in = ".$hosts->{$key}->{bytes_in}.", ";
		$host->packets_in($hosts->{$key}->{packets_in} || 0);
		$update_statement .= "packets_in = ".$hosts->{$key}->{packets_in}.", ";
                if ($hosts->{$key}->{bytes_ex} < $host->bytes_ex) {
			$update_statement .= "bytes_ex_base = ".($host->bytes_ex_base+$host->bytes_ex).", ";
                        $host->bytes_ex_base(($host->bytes_ex_base + $host->bytes_ex) || 0);
			$update_statement .= "packets_ex_base = ".($host->packets_ex_base+$host->packets_ex).", ";
                        $host->packets_ex_base(($host->packets_ex_base + $host->packets_ex) || 0);
                } else {
                }
		$host->bytes_ex($hosts->{$key}->{bytes_ex} || 0);
		$update_statement .= "bytes_ex = ".$hosts->{$key}->{bytes_ex}.", ";
		$host->packets_ex($hosts->{$key}->{packets_ex} || 0);
		$update_statement .= "packets_ex = ".$hosts->{$key}->{packets_ex}.", ";
		$host->bytes_ex_total(($hosts->{$key}->{bytes_ex} + $host->bytes_ex_base) || 0);
		$update_statement .= "bytes_ex_total = ".($hosts->{$key}->{bytes_ex}+$hosts->{$key}->{bytes_ex_base}).", ";

		my $upload_bytes = $host->bytes_ex + $host->bytes_ex_base;
		my $upload_packets = $host->packets_ex + $host->packets_ex_base;
		my $download_bytes = $host->bytes_in + $host->bytes_in_base;
		my $download_packets = $host->packets_in + $host->packets_in_base;

		$update_statement .= "ip = '$ip' WHERE ip = '$ip';\n";
		print $update_statement;
		$host->update;

		$csv_output .= "$ip,$upload_bytes,$upload_packets,$download_bytes,$download_packets\n";
	}
#	warn "Hotovo. Operacia trvala ".(time - $time_before)." sekund.\n";
#	$self->dbi_commit;	# commit
	return $csv_output;
}

sub clear_counters {			# vycisti pocitadla v databaze a aj na prislusnom routeri
	my $self = shift;
	my $subnet = NetAddr::IP->new($self->subnet);
	foreach my $address ($subnet->hostenum) {
		my $host = Host->search(ip => $address->addr)->next;
		if ($host != undef) {
			$host->bytes_in(0);
			$host->bytes_ex(0);
			$host->bytes_in_base(0);
			$host->bytes_ex_base(0);
			$host->packets_in(0);
			$host->packets_ex(0);
			$host->packets_in_base(0);
			$host->packets_ex_base(0);
			$host->update;
		}
			
	}
	my $gateway = $self->gatewayid;
	$gateway->command("sudo iptables -Z"); # TODO: pridat do tabulky stlpec so skriptom, ktory clearne iba potrebne chainy, napr. v pripade panamy /etc/sysconfig/iptables/sdjls.ubyt/forward.accounting
	$self->last_data_counter_reset('now');
	$self->update;

}

sub provide_accounting_info {		# vrati v poli prenesene data z databazy LANmgmt pre jednotlive adresy
	my $self = shift;
	my $subnet = $self->subnet;
	my @hosts = Host->retrieve_from_sql(qq{
		ip << '$subnet' AND
		(bytes_in > 0 OR
		bytes_in_base > 0 OR
		bytes_ex > 0 OR
		bytes_ex_base > 0)
		ORDER BY bytes_ex_total DESC
	});
	return @hosts;		# hashref by mohol byt rychlejsi, zvazit do buducna
}

sub set_gw_static_addr_uplinks {	# nastavi na uplinkoch vsetkych stohov MAC adresu default_gw staticky
					# (bezpecnostna feature)
        my $self = shift;
	my $MAC = $self->default_gateway_mac;
	my $VLAN = $self->segmentid;
	foreach my $stack ($self->stacks) {
		$stack->set_static_addr_uplink($MAC, $VLAN);
	}
}


1;

